/**   
 *  Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
* @Title: ThreadMethodRunner.java 
* @Package multithread.framework.threadmethod 
* @Description: TODO
* @author dailey  
* @date 2012-10-31
* @version V1.0   
*/
package multithread.framework.threadmethod;

import java.util.Iterator;
import java.util.concurrent.CompletionService;
import java.util.concurrent.Future;

import org.apache.log4j.Logger;

import multithread.framework.Runner;
import multithread.framework.ThreadRunListener;
import multithread.framework.annotation.NoneThreadGroup;
import multithread.framework.threadpool.ExecutorServiceFactory;
import multithread.framework.threadpool.MethodExecutorServiceProxy;
import multithread.framework.utility.Utilities;

/**
 * @author dailey
 *
 */
class ThreadMethodRunner extends Runner {
	private ThreadRunListener threadRunListener = null;
	private IContainer container = null;
	private Object testcase = null;

	/**
	 * @param threadRunListener
	 * @param container
	 * @param testcase
	 */
	protected ThreadMethodRunner(ThreadRunListener threadRunListener, IContainer container, Object testcase) {
		super();
		this.threadRunListener = threadRunListener;
		this.container = container;
		this.testcase = testcase;
	}

	protected void runMethodInMultiThread(final ThreadMethodContainer container, final Object testcase) {

		MethodExecutorServiceProxy methodService = ExecutorServiceFactory.createMethodExecutorServiceProxy(container,
				testcase);
		// all before
		methodService.fireThreadAllBefore();

		CompletionService<ThreadResult> completionService = ExecutorServiceFactory.createMethodCompletionService(
				methodService, container);

		// take thread method with assigned group
		for (ThreadMethodGroup methodGroup : container.getThreadGroups()) {
			// group start
			if (!methodGroup.isEmpty())
				methodService.fireThreadGroupBefore(methodGroup);
			invoke(methodGroup, completionService, testcase);
			// group after
			if (!methodGroup.isEmpty())
				methodService.fireThreadGroupAfter(methodGroup);
		}

		// take thread method without assigned group
		for (IThreadMethod threadmethod : container.getThreadMethods(NoneThreadGroup.class)) {
			invoke(threadmethod, completionService, testcase);
		}
		// shutdown thread pool and to fire all after method
		methodService.shutdown();
	}

	@SuppressWarnings("unchecked")
	private void invoke(IThreadMethod invoker, CompletionService<ThreadResult> completionService, Object testcase) {

		FutureQueue<ThreadResult> futureQueue = invoker.invoke(completionService, testcase);
		// can also use FutureQueue -> Future.get()
		int i = 0;
		while (i < invoker.getThreadCount()) {
			try {
				Future<ThreadResult> future = completionService.take();
				if (future != null)
					fireFutureComplete(invoker, future);
			} catch (InterruptedException e) {
				Logger.getLogger(getClass()).error(e);
				e.printStackTrace();
			} finally {
				i++;
			}
		}
		fireGroupFutureComplete(invoker, futureQueue);
	}

	private void fireGroupFutureComplete(IThreadMethod threadmethod, FutureQueue<ThreadResult> futureQueue) {
		if (threadRunListener != null)
			threadRunListener.onThreadGroupComplete(threadmethod, futureQueue);
	}

	private void fireFutureComplete(IThreadMethod threadmethod, Future<ThreadResult> future) {
		if (threadRunListener != null)
			threadRunListener.onThreadComplete(threadmethod, future);
	}

	/* (non-Javadoc)
	 * @see multithread.framework.Runner#run()
	 */
	@Override
	public void run() {
		if (container instanceof ThreadMethodContainer)
			runMethodInMultiThread((ThreadMethodContainer) container, testcase);
		else if (container instanceof TestCaseContainer) {
			for (Iterator<ThreadMethodContainer> it = ((TestCaseContainer) container).iterator(); it.hasNext();) {
				ThreadMethodContainer methodContainer = it.next();
				runMethodInMultiThread(methodContainer, Utilities.newInstance(methodContainer.getTestCaseClass()));
			}
		}
	}

}
